package com.hgts.service.impl;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.hgts.common.R;
import com.hgts.controller.CommonController;
import com.hgts.pojo.Book;
import com.hgts.mapper.BookMapper;
import com.hgts.pojo.Details;
import com.hgts.pojo.Type;
import com.hgts.pojo.dto.BookDto;
import com.hgts.pojo.dto.TypeDto;
import com.hgts.service.IBookService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hgts.service.IDetailsService;
import com.hgts.service.ITypeService;
import com.hgts.utils.SimpleRedisLock;
import com.hgts.utils.UserHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.hgts.utils.RedisConstants.*;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 凡哥哥
 * @since 2023-04-27
 */
@Service
@Slf4j
public class BookServiceImpl extends ServiceImpl<BookMapper, Book> implements IBookService {

    @Autowired
    private ITypeService typeService;

    @Autowired
    private IDetailsService detailsService;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    private String key;

    @Override
    @Transactional
    public R<Page> pageInfo(int page, int pageSize, String name) {
        // 获取当前登入用户id
        int userId = UserHolder.getUser().getId();
        // 1.从redis查询图书缓存
        key = BOOK_PAGE_KEY + page + pageSize;
        String json = stringRedisTemplate.opsForValue().get(key);
        if (name != null){
            //开启分页
            Page<Book> pageInfo  = new Page(page,pageSize);
            //条件构造器
            LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<>();
            lqw.eq(Book::getIsDelete,0);
            lqw.like(StringUtils.isNotEmpty(name),Book::getName,name);
            Page<Book> bookPage = this.page(pageInfo, lqw);
            //判断是否是空值
            if (bookPage == null){
                // 将控制写入redis
                stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
                //返回错误信息
                return R.error("没有数据");
            }
            //取出数据
            List<Book> records = bookPage.getRecords();
            List<Book> bookList = records.stream().map(item -> {
                //获取图书类型id
                Integer typeId = item.getTypeId();
                //根据图书类型id查询类型表
                Type type = typeService.getById(typeId);
                //获取图书类型
                String typeType = type.getType();
                //赋值
                item.setType(typeType);
                return item;
            }).collect(Collectors.toList());

            //分页重新赋值
            bookPage.setRecords(bookList);

            return R.success(bookPage);
        }
        // 2.判断是否存在
        if (StrUtil.isNotBlank(json)) {
            // 3. 存在直接返回
            return R.success(JSONUtil.toBean(json,Page.class));
        }
        //判断命中的是否是空值
        if (json != null){
            // 说明缓存的是""
            return R.error("没有数据");
        }
        //4.不存在，查询数据库 重建缓存
        // 4.1获取互斥锁
        String lockKey = LOCK_BOOK_KEY;
        boolean isLock = tryLock(lockKey);
        // 4.2.判断是否获取成功
        if (isLock) {
            Page<Book> bookPage = null;
            try {
                //开启分页
                Page<Book> pageInfo  = new Page(page,pageSize);
                //条件构造器
                LambdaQueryWrapper<Book> lqw = new LambdaQueryWrapper<>();
                lqw.eq(Book::getIsDelete,0);
                lqw.like(StringUtils.isNotEmpty(name),Book::getName,name);
                bookPage = this.page(pageInfo, lqw);
                //判断是否是空值
                if (bookPage == null){
                    // 将控制写入redis
                    stringRedisTemplate.opsForValue().set(key,"",CACHE_NULL_TTL, TimeUnit.MINUTES);
                    //返回错误信息
                    return R.error("没有数据");
                }
                //取出数据
                List<Book> records = bookPage.getRecords();
                List<Book> bookList = records.stream().map(item -> {
                    //获取图书类型id
                    Integer typeId = item.getTypeId();
                    //根据图书类型id查询类型表
                    Type type = typeService.getById(typeId);
                    //获取图书类型
                    String typeType = type.getType();
                    //赋值
                    item.setType(typeType);
                    return item;
                }).collect(Collectors.toList());

                //分页重新赋值
                bookPage.setRecords(bookList);
                // 有数据存入redis
                stringRedisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(bookPage),CACHE_BOOK_TTL,TimeUnit.MINUTES);
                return R.success(bookPage);
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                //释放锁
                unLock(lockKey);
            }
        }
        return R.error("服务器异常,请稍后重试");
    }


    private boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);
    }

    private void unLock(String key){
        stringRedisTemplate.delete(key);
    }

    @Override
    public R<List<TypeDto>> getType() {
        //查询图书类型
        List<Type> list = typeService.list();
        List<TypeDto> typeDtoList = list.stream().map(item -> {
            TypeDto typeDto = new TypeDto();
            //对象拷贝
            BeanUtils.copyProperties(item, typeDto, "description");
            return typeDto;
        }).collect(Collectors.toList());
        return R.success(typeDtoList);
    }

    @Override
    @Transactional
    public R<String> addBook(Book book) {
        //取出图书类型
        Integer typeId = book.getTypeId();
        //查询类型表获取id 存入图书信息表中
        LambdaQueryWrapper<Type> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.eq(Type::getId,typeId);
        Type one = typeService.getOne(queryWrapper);
        String type = one.getType();
        book.setType(type);
        //存入图书信息
        this.save(book);

        //更新缓存
        stringRedisTemplate.delete(key);
        return R.success("添加图书信息成功");
    }

    @Override
    @Transactional
    public R<Book> getBook(int id) {
        Book book = this.getById(id);
        //获取类型id
        Integer typeId = book.getTypeId();
        //查询图书类型表
        Type type = typeService.getById(typeId);
        String typeType = type.getType();
        book.setType(typeType);
        return R.success(book);
    }

    @Override
    public R<String> updateBook(Book book) {

        boolean update = this.updateById(book);
        if (update) {
            //更新成功
            //更新缓存
            stringRedisTemplate.delete(key);
            return R.success("图书信息更新成功");
        }

        return R.success("图书信息更新失败");
    }

    @Override
    public R<String> deleteBooks(int... ids) {
        //进行逻辑删除
        for (int id : ids) {
            LambdaUpdateWrapper<Book> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(Book::getId,id);
            updateWrapper.set(Book::getIsDelete,1);
            this.update(updateWrapper);
        }
        //更新缓存
        stringRedisTemplate.delete(key);
        return R.success("图书信息修改成功");
    }

    @Override
    public R<String> libraryBook(BookDto bookDto) {
        //结束前先查看库存
        Book book = this.getById(bookDto.getId());
        if (book.getStock() <= 0) {
            //库存不足
            return R.success("库存不足");
        }
        // 实现一人只能借阅一本书 加锁进行控制线程安全
        // 获取当前登录用户
        Integer userId = UserHolder.getUser().getId();
        //创建锁对象
        //创建锁对象 这个代码不用了，因为我们现在要使用真正的分布式锁
        //SimpleRedisLock lock = new SimpleRedisLock("book" + userId,stringRedisTemplate);
        //获取锁
        //boolean isLock = lock.tryLock(1200);
        RLock lock = redissonClient.getLock("lock:book:" + userId);
        //获取锁对象
        boolean isLock = lock.tryLock();
        if (!isLock) {
            //获取锁失败，返回错误信息
            return R.error("每人只能借阅一本书");
        }
        //synchronized (userId.toString().intern()){
            // 获取代理对象（事务）
        try {
            IBookService proxy = (IBookService) AopContext.currentProxy();
            return proxy.createBookOrder(bookDto);
        } finally {
            lock.unlock();
        }
        //}


    }

    @Transactional
    public R<String> createBookOrder(BookDto bookDto) {
        // 获取当前登录用户
        Integer userId = UserHolder.getUser().getId();
        synchronized (userId.toString().intern()){
            // 1.先查询数据库是否有此人的借阅信息
            LambdaQueryWrapper<Details> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Details::getIsDelete,0);
            queryWrapper.eq(Details::getUserId,userId);
            Details one = detailsService.getOne(queryWrapper);
            if (one != null){
                return R.error("每人只能借阅一本图书");
            }
            //此时为借阅图书
            LambdaUpdateWrapper<Book> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.eq(Book::getId, bookDto.getId());
            //乐观锁 cas控制
            updateWrapper.setSql("stock = stock - 1").gt(Book::getStock,0);
            boolean update = this.update(updateWrapper);
            Details details = new Details();
            //获取用户信息
            String username = UserHolder.getUser().getName();
            details.setUsername(username);
            //获取图书名称
            details.setBookName(bookDto.getName());
            //开始时间
            details.setStartTime(bookDto.getStartTime());
            //还书时间
            details.setEndTime(bookDto.getEndTime());
            //这本书的id 方便归还图书
            details.setBookId(bookDto.getId());
            //保存结束用户id
            details.setUserId(userId);
            //执行插入
            detailsService.save(details);
            if (update) {
                //更新缓存
                stringRedisTemplate.delete(key);
                return R.success("图书借阅成功");
            }else {
                return R.success("库存不足");
            }
        }

    }
    @Override
    @Transactional
    public R<String> returnBook(int id) {
        //先根据id查询这本书的详细借阅信息
        Details details = detailsService.getById(id);
        //获取图书id 查询图书信息
        Book book = this.getById(details.getBookId());
        //把这本书的库存+1
        book.setStock(book.getStock() + 1);
        this.updateById(book);
        //进行逻辑删除
        details.setIsDelete(1);
        boolean isSuccess = detailsService.updateById(details);
        if (isSuccess){
            //更新缓存
            stringRedisTemplate.delete(key);
            return R.success("归还成功");
        }
        return R.success("服务器异常");
    }

    @Override
    public R<String> dleBooks(int... ids) {
        for (int id : ids) {
            LambdaUpdateWrapper<Details> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.set(Details::getIsPromise,0);
            updateWrapper.set(Details::getIsDelete,1);
            updateWrapper.eq(Details::getId,id);
            boolean update = detailsService.update(updateWrapper);
            if (update){
                //根据id查询图书id
                Details details = detailsService.getById(id);
                int bookId = details.getBookId();
                //根据bookId更改图书库存
                this.update().setSql("stock = stock + 1").eq("id",bookId).update();
                //更新缓存
                stringRedisTemplate.delete(key);
                return R.success("操作成功");
            }
        }
        return R.error("操作失败");
    }

    @Override
    public R<Page> pageInfoWx(Long id,Integer page, Integer pageSize) {
        //开启分页
        Page<Book> bookPage = new Page<>(page,pageSize);
        LambdaQueryWrapper<Book> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.eq(Book::getTypeId,id);
        Page<Book> page1 = this.page(bookPage, queryWrapper);
        return R.success(page1);
    }
}

